# @Time:        2024/7/7 上午10:22
# @USER:        lihuizhuo
# @Site:        www.swust.edu.cn
# @File:        M
# @Software:    PyCharm
# @license:     Copyright(C), swust
# @Author:      huizhuo
# @Email:       huizhuoli@foxmail.com
# @Version:     V1.1.0

import logging

import pygame
from pygame.locals import *

import pytmx
from pytmx import TiledImageLayer, TiledObjectGroup, TiledTileLayer
from pytmx.util_pygame import load_pygame

logger = logging.getLogger(__name__)


def init_screen(width, height):
    """Set the screen mode
    This function is used to handle window resize events
    """
    return pygame.display.set_mode((width, height), pygame.RESIZABLE)


class TiledRenderer(object):
    """
    Super simple way to render a tiled map
    """

    def __init__(self, filename) -> None:
        tm = load_pygame(filename)

        # self.size will be the pixel size of the map
        # this value is used later to render the entire map to a pygame surface
        self.pixel_size = tm.width * tm.tilewidth, tm.height * tm.tileheight
        self.tmx_data = tm

    def render_map(self, surface) -> None:
        """Render our map to a pygame surface

        Feel free to use this as a starting point for your pygame app.
        This method expects that the surface passed is the same pixel
        size as the map.

        Scrolling is a often requested feature, but pytmx is a map
        loader, not a renderer!  If you'd like to have a scrolling map
        renderer, please see my pyscroll project.
        """

        # fill the background color of our render surface
        if self.tmx_data.background_color:
            surface.fill(pygame.Color(self.tmx_data.background_color))

        # iterate over all the visible layers, then draw them
        for layer in self.tmx_data.visible_layers:
            # each layer can be handled differently by checking their type

            if isinstance(layer, TiledTileLayer):
                self.render_tile_layer(surface, layer)

            elif isinstance(layer, TiledObjectGroup):
                self.render_object_layer(surface, layer)

            elif isinstance(layer, TiledImageLayer):
                self.render_image_layer(surface, layer)

    def render_tile_layer(self, surface, layer) -> None:
        """Render all TiledTiles in this layer"""
        # deref these heavily used references for speed
        tw = self.tmx_data.tilewidth
        th = self.tmx_data.tileheight
        surface_blit = surface.blit

        # iterate over the tiles in the layer, and blit them
        if self.tmx_data.orientation == "orthogonal":
            for x, y, image in layer.tiles():
                surface_blit(image, (x * tw, y * th))
        elif self.tmx_data.orientation == "isometric":
            ox = self.pixel_size[0] // 2
            tw2 = tw // 2
            th2 = th // 2
            for x, y, image in layer.tiles():
                sx = x * tw2 - y * tw2
                sy = x * th2 + y * th2
                surface_blit(image, (sx + ox, sy))

    def render_object_layer(self, surface, layer) -> None:
        """Render all TiledObjects contained in this layer"""
        # deref these heavily used references for speed
        draw_lines = pygame.draw.lines
        surface_blit = surface.blit

        # these colors are used to draw vector shapes,
        # like polygon and box shapes
        rect_color = (0, 255, 0)

        # iterate over all the objects in the layer
        # These may be Tiled shapes like circles or polygons, GID objects, or Tiled Objects
        for obj in layer:
            logger.info(obj)

            # objects with points are polygons or lines
            if obj.image:
                # some objects have an image; Tiled calls them "GID Objects"
                surface_blit(obj.image, (obj.x, obj.y))

            else:
                # use `apply_transformations` to get the points after rotation
                draw_lines(
                    surface, rect_color, obj.closed, obj.apply_transformations(), 3
                )

    def render_image_layer(self, surface, layer) -> None:
        if layer.image:
            surface.blit(layer.image, (0, 0))


class SimpleTest(object):
    """Basic app to display a rendered Tiled map"""

    def __init__(self, filename) -> None:
        self.renderer = None
        self.running = False
        self.dirty = False
        self.exit_status = 0
        self.load_map(filename)

    def load_map(self, filename) -> None:
        """Create a renderer, load data, and print some debug info"""
        self.renderer = TiledRenderer(filename)

        # logger.info("Objects in map:")
        # for obj in self.renderer.tmx_data.objects:
        #     logger.info(obj)
        #     for k, v in obj.properties.items():
        #         logger.info("%s\t%s", k, v)
        #
        # logger.info("GID (tile) properties:")
        # for k, v in self.renderer.tmx_data.tile_properties.items():
        #     logger.info("%s\t%s", k, v)
        #
        # logger.info("Tile colliders:")
        # for k, v in self.renderer.tmx_data.get_tile_colliders():
        #     logger.info("%s\t%s", k, list(v))

    def draw(self, surface) -> None:
        """Draw our map to some surface (probably the display)"""
        # first we make a temporary surface that will accommodate the entire
        # size of the map.
        # because this demo does not implement scrolling, we render the
        # entire map each frame
        temp = pygame.Surface(self.renderer.pixel_size)

        # render the map onto the temporary surface
        self.renderer.render_map(temp)

        # now resize the temporary surface to the size of the display
        # this will also 'blit' the temp surface to the display
        pygame.transform.smoothscale(temp, surface.get_size(), surface)

        # display a bit of use info on the display
        f = pygame.font.Font(pygame.font.get_default_font(), 20)
        i = f.render("press any key for next map or ESC to quit", 1, (180, 180, 0))
        surface.blit(i, (0, 0))

    def handle_input(self) -> None:
        try:
            event = pygame.event.wait()

            if event.type == QUIT:
                self.exit_status = 0
                self.running = False

            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    self.exit_status = 0
                    self.running = False
                else:
                    self.running = False

            elif event.type == VIDEORESIZE:
                init_screen(event.w, event.h)
                self.dirty = True

        except KeyboardInterrupt:
            self.exit_status = 0
            self.running = False

    def run(self):
        """This is our app main loop"""
        self.dirty = True
        self.running = True
        self.exit_status = 1

        while self.running:
            self.handle_input()

            # we don't want to constantly draw on the display, as that is way
            # inefficient.  so, this 'dirty' values is used.  If dirty is True,
            # then re-render the map, display it, then mark 'dirty' False.
            if self.dirty:
                self.draw(screen)
                self.dirty = False
                pygame.display.flip()

        return self.exit_status


if __name__ == "__main__":
    import glob
    import os.path

    pygame.init()
    pygame.font.init()
    screen = init_screen(600, 600)
    pygame.display.set_caption("PyTMX Map Viewer")
    logging.basicConfig(level=logging.DEBUG)

    logger.info(pytmx.__version__)

    # loop through a bunch of maps in the maps folder
    try:
        filename = "../resources/tmx/tunnel_map.tmx"
        SimpleTest(filename).run()

    except:
        pygame.quit()
        raise